# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Copyright (C) 2025 ZengFanyu
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import datetime
import os
from tkinter import messagebox

class RegressionAnalyzer:
    def __init__(self, root):
        self.root = root
        self.root.title("最小二乘法回归分析工具")
        self.root.geometry("1000x700")
        
        # 存储原始数据和处理后的数据
        self.original_x = []
        self.original_y = []
        self.processed_x = []
        self.processed_y = []
        
        # 创建GUI
        self.create_widgets()
        
    def create_widgets(self):
        # 创建左侧输入面板
        input_frame = ttk.LabelFrame(self.root, text="数据输入")
        input_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
        
        ttk.Label(input_frame, text="输入X值 (用任意非数字字符分隔):").grid(row=0, column=0, padx=5, pady=5, sticky="w")
        self.x_input = tk.Text(input_frame, width=30, height=10)
        self.x_input.grid(row=1, column=0, padx=5, pady=5)
        
        ttk.Label(input_frame, text="输入Y值 (用任意非数字字符分隔):").grid(row=2, column=0, padx=5, pady=5, sticky="w")
        self.y_input = tk.Text(input_frame, width=30, height=10)
        self.y_input.grid(row=3, column=0, padx=5, pady=5)
        
        # 数据处理面板
        processing_frame = ttk.LabelFrame(input_frame, text="数据处理")
        processing_frame.grid(row=4, column=0, padx=5, pady=5, sticky="nsew")
        
        # X数据处理选项
        ttk.Label(processing_frame, text="处理X数据:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
        self.x_process_var = tk.StringVar()
        x_process_combo = ttk.Combobox(processing_frame, textvariable=self.x_process_var)
        x_process_combo['values'] = ('Original Data', '取自然对数', '取倒数', '平方', '平方根', "摄氏度转开氏度")
        x_process_combo.current(0)
        x_process_combo.grid(row=0, column=1, padx=5, pady=5)
        
        # Y数据处理选项
        ttk.Label(processing_frame, text="处理Y数据:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
        self.y_process_var = tk.StringVar()
        y_process_combo = ttk.Combobox(processing_frame, textvariable=self.y_process_var)
        y_process_combo['values'] = ('Original Data', '取自然对数', '取倒数', '平方', '平方根', "摄氏度转开氏度")
        y_process_combo.current(0)
        y_process_combo.grid(row=1, column=1, padx=5, pady=5)
        
        # 按钮区域
        button_frame = ttk.Frame(input_frame)
        button_frame.grid(row=5, column=0, padx=5, pady=10)
        
        ttk.Button(button_frame, text="分析数据", command=self.analyze_data).grid(row=0, column=0, padx=5)
        ttk.Button(button_frame, text="还原数据", command=self.restore_data).grid(row=0, column=1, padx=5)
        ttk.Button(button_frame, text="保存结果", command=self.save_results).grid(row=0, column=2, padx=5)
        
        # 添加版权声明区域
        copyright_text = "Copyright © 2025 ZengFanyu <zeng-fan-yu@outlook.com>. \nAll rights reserved under GNU Affero General Public License v3.0"
        copyright_label = ttk.Label(input_frame, text=copyright_text, justify=tk.CENTER, font=("Arial", 8))
        copyright_label.grid(row=6, column=0, padx=5, pady=(0, 5))

        # 创建右侧图表区域
        plot_frame = ttk.LabelFrame(self.root, text="result")
        plot_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
        
        # 创建Matplotlib图表
        self.figure = plt.Figure(figsize=(6, 5), dpi=100)
        self.plot = self.figure.add_subplot(111)
        self.canvas = FigureCanvasTkAgg(self.figure, plot_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
        # 创建结果显示区域
        self.result_text = tk.Text(plot_frame, width=50, height=10)
        self.result_text.pack(fill=tk.X, padx=5, pady=5)
        
        # 配置网格权重
        self.root.grid_columnconfigure(0, weight=1)
        self.root.grid_columnconfigure(1, weight=2)
        self.root.grid_rowconfigure(0, weight=1)
    
    def process_data(self, data, process_type):
        """根据选定的处理方式处理数据"""
        processed_data = np.array(data, dtype=float)
        
        if process_type == '取自然对数':
            # 检查所有值是否为正
            if np.any(processed_data <= 0):
                messagebox.showerror("错误", "取自然对数时，所有值必须为正数！")
                return None
            processed_data = np.log(processed_data)
        elif process_type == '取倒数':
            # 检查是否有0值
            if np.any(processed_data == 0):
                messagebox.showerror("错误", "取倒数时，不能有0值！")
                return None
            processed_data = 1 / processed_data
        elif process_type == '平方':
            processed_data = processed_data ** 2
        elif process_type == '平方根':
            # 检查所有值是否为非负
            if np.any(processed_data < 0):
                messagebox.showerror("错误", "取平方根时，所有值必须为非负数！")
                return None
            processed_data = np.sqrt(processed_data)
        elif process_type == "摄氏度转开氏度":
            processed_data += 273 
        
        return processed_data.tolist()
    
    def parse_input(self, text_input):
        """解析输入文本，支持任何非数字字符作为分隔符"""
        import re
        # 使用正则表达式匹配所有数字（包括小数和负数）
        pattern = r'-?\d+\.?\d*|-?\.\d+'
        # 查找所有匹配的数字
        numbers = re.findall(pattern, text_input)
        # 转换为浮点数
        try:
            return [float(num) for num in numbers]
        except ValueError:
            messagebox.showerror("输入错误", "请输入有效的数值")
            return []
    
    def analyze_data(self):
        """Analyze data and plot regression line"""
        # 获取并解析X和Y输入
        x_input = self.x_input.get("1.0", tk.END)
        y_input = self.y_input.get("1.0", tk.END)
        
        self.original_x = self.parse_input(x_input)
        self.original_y = self.parse_input(y_input)
        
        # 检查数据有效性
        if not self.original_x or not self.original_y:
            return
        
        if len(self.original_x) != len(self.original_y):
            messagebox.showerror("数据错误", "X和Y的数据点数量必须相同！")
            return
        
        # 处理数据
        self.processed_x = self.process_data(self.original_x, self.x_process_var.get())
        self.processed_y = self.process_data(self.original_y, self.y_process_var.get())
        
        if self.processed_x is None or self.processed_y is None:
            return
        
        # 计算线性回归
        x = np.array(self.processed_x)
        y = np.array(self.processed_y)
        
        # 计算相关参数
        n = len(x)
        if n < 2:
            messagebox.showerror("数据不足", "至少需要两个数据点来计算回归线！")
            return
            
        mean_x = np.mean(x)
        mean_y = np.mean(y)
        
        # 计算回归系数
        numerator = np.sum((x - mean_x) * (y - mean_y))
        denominator = np.sum((x - mean_x) ** 2)
        
        if denominator == 0:
            messagebox.showerror("计算错误", "X值全部相同，无法计算回归线！")
            return
            
        slope = numerator / denominator
        intercept = mean_y - slope * mean_x
        
        # 计算R平方
        y_pred = slope * x + intercept
        ss_total = np.sum((y - mean_y) ** 2)
        ss_residual = np.sum((y - y_pred) ** 2)
        
        if ss_total == 0:
            r_squared = 1  # 完美拟合
        else:
            r_squared = 1 - (ss_residual / ss_total)
        
        # Display results
        result_text = f"Regression Equation: y = {slope:.6f}x + {intercept:.6f}\n"
        result_text += f"Slope (m): {slope:.6f}\n"
        result_text += f"Intercept (b): {intercept:.6f}\n"
        result_text += f"Correlation Coefficient (R): {np.sqrt(abs(r_squared)):.6f}\n"
        result_text += f"Coefficient of Determination (R²): {r_squared:.6f}\n"
        
        self.result_text.delete("1.0", tk.END)
        self.result_text.insert(tk.END, result_text)
        
        # Plot chart
        self.plot.clear()
        self.plot.scatter(x, y, color='blue', label='Data Points')
        
        # Plot regression line
        x_line = np.linspace(min(x), max(x), 100)
        y_line = slope * x_line + intercept
        self.plot.plot(x_line, y_line, 'r-', label=f'y = {slope:.4f}x + {intercept:.4f}')
        
        # Add labels and legend
        x_label = "X"
        y_label = "Y"

        self.plot.set_xlabel(x_label)
        self.plot.set_ylabel(y_label)
        self.plot.set_title("Analysis Result")
        self.plot.legend()
        self.plot.grid(True)
        
        self.canvas.draw()
    
    def restore_data(self):
        """还原到原始数据"""
        if not self.original_x or not self.original_y:
            messagebox.showinfo("提示", "没有可还原的数据")
            return
            
        # 将X和Y处理选项重置为"Original Data"
        self.x_process_var.set("Original Data")
        self.y_process_var.set("Original Data")
        
        # 重新分析数据
        self.analyze_data()
    
    def save_results(self):
        """保存结果到文件"""
        if not hasattr(self, 'processed_x') or not self.processed_x:
            messagebox.showinfo("提示", "没有可保存的结果")
            return
            
        # 创建保存文件夹
        save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results")
        os.makedirs(save_dir, exist_ok=True)
        
        # 创建带时间戳的文件名
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"regression_analysis_{timestamp}"
        
        # 保存图表
        fig_path = os.path.join(save_dir, f"{filename}.png")
        self.figure.savefig(fig_path)
        
        # 保存数据和结果
        data_path = os.path.join(save_dir, f"{filename}.txt")
        
        with open(data_path, 'w', encoding='utf-8') as f:  # 指定使用UTF-8编码
            # 写入处理信息
            f.write(f"X数据处理: {self.x_process_var.get()}\n")
            f.write(f"Y数据处理: {self.y_process_var.get()}\n\n")
            
            # 写入结果
            f.write(self.result_text.get("1.0", tk.END))
            f.write("\n\n")
            
            # 写入原始数据
            f.write("Original Data:\n")
            f.write("X,Y\n")
            for i in range(len(self.original_x)):
                f.write(f"{self.original_x[i]},{self.original_y[i]}\n")
            
            # 写入处理后的数据
            f.write("\n处理后数据:\n")
            f.write("X,Y\n")
            for i in range(len(self.processed_x)):
                f.write(f"{self.processed_x[i]},{self.processed_y[i]}\n")
        
        messagebox.showinfo("保存成功", f"结果已保存到:\n{fig_path}\n{data_path}")


if __name__ == "__main__":
    root = tk.Tk()
    app = RegressionAnalyzer(root)
    root.mainloop()
